package com.miao.edu.gateway.filter;

import com.miao.edu.authority.dto.JwtDto;
import com.miao.edu.authority.remote.OauthRemoteService;
import com.miao.edu.gateway.service.IPermissionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.LinkedHashSet;

/**
 * @author niemiao
 */
@Slf4j
@Configuration
@ComponentScan(basePackages = "com.miao.edu")
public class AccessGatewayFilter implements GlobalFilter {

    private static final String X_USER_ID = "x-user-id";
    private static final String X_USER_NAME = "x-user-name";
    private static final String X_USER_IP = "x-user-ip";
    private static final String BOSS_PATH_PREFIX = "/boss";


    @Autowired
    private OauthRemoteService oauthRemoteService;

    @Autowired
    private IPermissionService permissionService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取request对象，从中可以获取认证信息
        ServerHttpRequest request = exchange.getRequest();
        // 获取路径
        String path = request.getPath().value();
        // 获取认证信息
        String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);

        // 如果头信息中包含认证信息，则进行验证
        if (StringUtils.isNotBlank(authorization)) {
            return validateAuthentication(exchange, chain, authorization, path);
        }
        return unAuthentication(exchange);
    }

    /**
     * 不验证的时候,直接放行
     * @param exchange
     * @return
     */
    private Mono<Void> unAuthentication(ServerWebExchange exchange) {
        return rebuildExchange(exchange,HttpStatus.UNAUTHORIZED);
    }


    private Mono<Void> rebuildExchange(ServerWebExchange exchange, HttpStatus httpStatus) {
        exchange.getResponse().setStatusCode(httpStatus);
        DataBuffer buffer = exchange.getResponse()
                .bufferFactory().wrap(httpStatus.getReasonPhrase().getBytes());
        return exchange.getResponse().writeWith(Flux.just(buffer));
    }


    /**
     * 验证信息
     * @param exchange
     * @param chain
     * @param authorization
     * @param path
     * @return
     */
    private Mono<Void> validateAuthentication(ServerWebExchange exchange, GatewayFilterChain chain, String authorization, String path) {


        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();
        String ip = request.getRemoteAddress().getAddress().getHostAddress();
        // 获取原始的url。
        LinkedHashSet<URI> originUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        // 处理jwt
        String userId = null;
        String userName = null;
        try {
            // 解析 jwt 获取用户名密码信息
            JwtDto jwt = oauthRemoteService.getJwt(authorization);
            if (jwt != null) {
                userName = jwt.getUsername();
                userId = jwt.getUserId();
            }
            ServerHttpRequest.Builder mutate = request.mutate();

            if (StringUtils.isNotBlank(userName)) {
                mutate.header(X_USER_NAME,userName);
            }
            if (StringUtils.isNotBlank(userId)) {
                mutate.header(X_USER_ID,userId);
            }
            if (StringUtils.isNotBlank(ip)) {
                mutate.header(X_USER_IP,ip);
            }
            exchange = exchange.mutate().request(mutate.build()).build();
        }catch (Exception var4) {
            // 如果不是忽略url，则返回401，需要登录
            if (!oauthRemoteService.ignoreAuthentication(path)) {
                return unauthorized(exchange);
            }
        }

        // 如果是忽略的url，在填充header中的登录用户信息后直接返回
        if (oauthRemoteService.ignoreAuthentication(path)) {
            return chain.filter(exchange);
        }

        boolean hasPermission = true;
        // 管理后台才需要判断权限，其他请求只需要验证jwt
        if (isBossPath(originUrl,path)) {
            //ToDo  进行后台系统的权限验证
            // 将原始url赋值给当前url。
            path = BOSS_PATH_PREFIX.concat(path);
            hasPermission = permissionService.permission(authorization, userId, path, method);
            log.info("Check boss permission. userId:{}, have permission:{}, url:{}, method:{}", userId, hasPermission, path, method);
        }


        if (hasPermission && StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userName)){
            return chain.filter(exchange);
        }

        return forbidden(exchange);
    }

    /**
     * 未登录或token状态异常，返回401
     *
     * @param exchange
     */
    private Mono<Void> unauthorized(ServerWebExchange exchange) {
        return rebuildExchange(exchange, HttpStatus.UNAUTHORIZED);
    }


    /**
     * 根据原始url判断是否请求的后台管理功能url。
     * <p>filter中如果使用了StripPrefix，url会被截取前一个"/"节点。无法检测到url是否包含/boss。这里获取原始的url进行判断</p>
     *
     * @param originUrl 获取的原始url
     * @param url       通过一些filter处理过的url。
     * @return
     */
    private boolean isBossPath(LinkedHashSet<URI> originUrl, String url) {
        if (url.startsWith(BOSS_PATH_PREFIX)) {
            return true;
        }
        for (URI uri : originUrl) {
            if (uri.getPath().startsWith(BOSS_PATH_PREFIX)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 未通过权限验证，返回forbidden
     *
     * @param exchange
     * @return
     */
    private Mono<Void> forbidden(ServerWebExchange exchange) {
        return rebuildExchange(exchange, HttpStatus.FORBIDDEN);
    }

}
